10-1

「通用表示法」或「通用式」(Regular expressions)是在 UNIX 世界中發展出來的字串比對技巧,其基本概念是用一套格式簡單、但功能強大的符號來比對複雜的字串,並可對符合比對條件的字串進行修改或其他運算。事實上,UNIX 的許多軟體或指令都支援通用表示法,例如 grep、sed、awk、ed、vi、emacs 等。(但是這些東西大概只有像我這樣的 LKK 才會用吧。)

Hint
若按照字面來翻譯,Regular expressions 應該翻成「正規表示法」或「正規式」,但是我們使用「通用表示法」或「通用式」似乎更能適切地表達其功能。

目前各個主流瀏覽器都支援 JavaScript 的通用表示法,特別適用於表單資料的驗證與修改。事實上,JavaScript 的通用表示法和 Perl 以及其他 UNIX 相關指令幾乎一模一樣,因此,在本章學到的通用表示法,也可以完全適用於 Perl 或 UNIX 相關指令。(一魚兩吃,真是太棒了!)

Hint
VBScript 的通用式在功能上和 JavaScript 完全相同,只不過命令格式有所不同,有興趣的讀者,可以參考網路相關資料。

JavaScript 的通用式是一個內建的物件,其建構函數(Construction functoin)為 RegExp,典型用法如下:

re = new RegExp("pattern", "flag")
上述用法也可以簡寫成下列格式:
re = /pattern/flag
其中,pattern 是通用表示法的字串,flag 則是比對的方式。flag 的值可能有三種,分別解釋如下:

舉例來說,我們的身份證字號的基本格式,是由一個英文字母加上九個數字組合而成,如果我們要求使用者輸入身份證字號,就可以使用 JavaScript 的通用表示法來驗證其格式的正確性。例如,我們可用下列表單來要求使用者輸入身份證字號:

Example(regExpID01.htm):

在上例中,我們只要按下「驗證」的按鈕,就會呼叫 checkID() 函數來對文字欄位中的身份證字號進行驗證。相關原始碼如下:

原始檔(regExpID01.htm):(灰色區域按兩下即可拷貝)
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
</head>

<body>
<h2 align=center>通用式:簡易身份證字號驗證</h2>
<hr>

<script>
function checkID(string) {
	re = /^[A-Z]\d{9}$/;
	if (re.test(string))
		alert("成功!符合「" + re + "」的格式!");
	else
		alert("失敗!不符合「" + re + "」的格式!");
}
</script>
身份證字號(第一個英文字母需大寫):<input id=idNumber value=A12345678>
<input type=button value="驗證" onClick="checkID(idNumber.value)">

<hr>
</body>
</html>

在上述範例中,/^[A-Z]\d{9}$/ 就是一個通用式,說明如下:

由上述說明,可知 /^[A-Z]\d{9}$/ 就代表可以比對身份證字號的通用式。此外,idNumber.value 代表使用者輸入的字串,re.test(string) 則是通用式 re 的一個方法,會傳回 true 或 false,代表比對是否成功。若要不限定是大寫英文字母,只需將上述範例的通用式改成 /^[a-zA-Z]\d{9}$/ 就可以了!

Hint
注意:使用 ^ 和 $,代表「頭尾都要符合」。若不加入 ^ 和 $,那麼 /[A-Z]d{9}/ 就會比對到任何含有身份證字號的字串,例如 AGF123456789 或是 F1234567890 等。因此,加入 ^ 和 $ 可保證比對正確的字串一定是由一個大寫英文字母加上九個數字所構成。

事實上,身份證字號本身就有內在的編碼規則,這些規則和使用者的性別有關,因此若要實現完整的表單驗證,就必須應用完整的身份證編碼規則,讀者可參考本章的最後一節。

另一個簡單的例子,是要求使用者輸入信用卡號碼,這是一組 16 個數字的號碼,例如:

Example(regExpCreditCardNumber01.htm):

當我們按下「驗證」按鈕時,JavaScript 會呼叫函數 checkCreditCard( ) 來對填入的資料進行驗證。相關原始碼如下:

原始檔(regExpCreditCardNumber01.htm):(灰色區域按兩下即可拷貝)
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
</head>

<body>
<h2 align=center>通用式:簡易信用卡卡號驗證</h2>
<hr>

<script>
function checkCreditCard(string) {
	re = /^\d{4}-\d{4}-\d{4}-\d{4}$/;
//	re = /^(\d{4}-){3}\d{4}$/;		// 這種寫法也可以!
	if (re.test(string))
		alert("成功!符合「" + re + "」的格式!");
	else
		alert("失敗!不符合「" + re + "」的格式!");
}
</script>
信用卡號碼:<input id=creditCardNumber value=1234-5678-9012-3456>
<input type=button value="驗證" onClick="checkCreditCard(creditCardNumber.value)">

<hr>
</body>
</html>

在上例中,很顯然地,/^\d{4}-\d{4}-\d{4}-\d{4}$/ 就代表正確的信用卡格式。很明顯的,使用通用式會讓程式碼簡潔很多,而且會大大提高程式碼的正確性。(請和前面章節的類似範例 formValidation02.htm 比較看看。)但要注意的是,信用卡卡號本身就有內在的較複雜編碼規則,因此若要實現完整的表單驗證,就必須應用完整的信用卡卡號編碼規則,讀者可參考本章的最後一節。

如果重複的部分多於一個字母,我們就必須將需要重複的部分放在小括號內,再加上由大括號包夾的重複次數,例如,上述範例的通用式 /^\d{4}-\d{4}-\d{4}-\d{4}$/,也可以寫成 /^(\d{4}-){3}\d{4}$/,請試試看!

下一個例子,則是用通用表示法來驗證使用者的英文名字,例如:

Example(regExpEnglishName01.htm):

當我們按下「驗證」按鈕時,JavaScript 會呼叫函數 checkEnglishName( ) 來對填入的資料進行驗證。相關原始碼如下:

原始檔(regExpEnglishName01.htm):(灰色區域按兩下即可拷貝)
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
</head>

<body>
<h2 align=center>通用式:簡易英文名字驗證</h2>
<hr>

<script>
function checkEnglishName(string) {
	re1 = /^[A-Za-z\-]+\s+[A-Za-z\-]+$/;
	re2 = /^[A-Za-z\-]+\s+[A-Za-z\-]+\s+[A-Za-z\-]+$/;
	if (re1.test(string) || re2.test(string))
		alert("成功!符合「" + re1 + "」或「" + re2 + "」的格式!");
	else
		alert("失敗!不符合「" + re1 + "」或「" + re2 + "」的格式!");
}
</script>
你的英文全名(格式:First Last 或 First Middle Last):<input id=englishName value="Jyh-Shing Roger Jang">
<input type=button value="驗證" onClick="checkEnglishName(englishName.value)">

<hr>
</body>
</html>

對於上述範例程式,我們說明如下:

因此 re1 = /^[A-Za-z\-]+\s+[A-Za-z\-]+$/ 可以比對由兩個字彙所形成的英文名字,例如 Michael Jordan;而 re2 = /^[A-Za-z\-]+\s+[A-Za-z\-]+\s+[A-Za-z\-]+$/ 則可以比對由三個字彙所形成的英文名字,例如,Jyh-Shing Roger Jang。

下一個例子,則是用通用表示法來驗證電子郵件,例如:

Example(regExpEmail01.htm):

當我們按下「驗證」按鈕時,JavaScript 會呼叫函數 checkEmail( ) 來對填入的資料進行驗證。相關原始碼如下:

原始檔(regExpEmail01.htm):(灰色區域按兩下即可拷貝)
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
</head>

<body>
<h2 align=center>通用式:簡易電子郵件格式驗證</h2>
<hr>

<script>
function checkEmail(string) {
	re = /^.+@.+\..{2,3}$/;
	if (re.test(string))
		alert("成功!符合「" + re + "」的格式!");
	else
		alert("失敗!不符合「" + re + "」的格式!");
}
</script>
電子郵件:<input id=email value="jang@cs.nthu.edu.t">
<input type=button value="驗證" onClick="checkEmail(email.value)">

<hr>
</body>
</html>

對於此範例所用到的通用式 /^.+@.+\..{2,3}$/,說明如下:

因此 /^.+@.+\..{2,3}$/ 可用來比對一般的 email 帳號,例如 test@cs.nthu.edu.tw 或是 roger_jang@mathworks.com 等,但是此通用式並非滴水不漏,有些不合格的 email 帳號也會比對成功,例如 " @math.com",或是 "test@ .tw",或是 "aa@bb.zz"。若要避開含有空白的 email 帳號,請見下列範例:

Example(regExpEmail02.htm):

相關原始碼如下:

原始檔(regExpEmail02.htm):(灰色區域按兩下即可拷貝)
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
</head>

<body>
<h2 align=center>通用式:電子郵件格式驗證(可以避開含有空白的電子郵件帳號)</h2>
<hr>

<script>
function checkEmail(string) {
	re = /^[^\s]+@[^\s]+\.[^\s]{2,3}$/;
	if (re.test(string))
		alert("成功!符合「" + re + "」的格式!");
	else
		alert("失敗!不符合「" + re + "」的格式!");
}
</script>
電子郵件:<input id=email value="jang@cs.n thu.edu.tw">
<input type=button value="驗證" onClick="checkEmail(email.value)">

<hr>
</body>
</html>

對於此範例所用到的通用式 /^[^\s]+@[^\s]+\.[^\s]{2,3}$/,說明如下:

Hint
請注意:^ 在一般通用表示法的意義是「字串開始的位置」,但是一旦放在中括弧內,則是代表「否定」或「非」。

在以下的範例中,我們設計了一個表單,可以讓使用者輸入任意字串、通用式,以及比對選項,並在通用式比對後,列出比對到的字串,讀者們可以利用此範例,反覆演練,以增進對於通用式的瞭解:

Example(regExpTest01.htm):

上述範例的原始檔如下:

原始檔(regExpTest01.htm):(灰色區域按兩下即可拷貝)
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
</head>

<body>
<h2 align=center>通用式:完整測試頁</h2>
<hr>

<script>
function showMatched(form){
	var regexp = new RegExp(form.pattern.value, form.flag.value);
	var str = form.string.value;
	var matched = str.match(regexp);
	if (matched) {
		var dispstr = matched.length + " 個比對到的字串:";
		for (var i=0; i<matched.length; i++)
			dispstr = dispstr + "\n" + matched[i];  
		alert(dispstr);
	} else
		alert("沒有比對到任何字串!");
}
</script>
<form>
<table align=center>
<tr><td align=right>字串:
<td><input type=text size=30 name=string value="There are 10 rookies coming at 3 o'clock!">
<tr><td align=right>通用式:
<td><input type=text size=30 name=pattern value=" \w+ "> (範例:\d{2,3}, T.*a, T.*?a, \b\w+\b)
<tr><td align=right>選項:
<td><input type=text size=30 name=flag value="g"> (g, i, or gi)
<tr><td align=right><br>
<td><input type="button" value="顯示比對到的字串" onClick="showMatched(this.form)"><input type="reset">
</table>
</form>

<hr>
</body>
</html>

在上述範例中,我們使用了字串的 match() 方法,來對通用式進行比對,因此 matched = str.match(regexp) 可將比對到的字串送到一個陣列,以便後續處理。

Hint
在上述範例中,我們的通用式是「 w+ 」(注意前後都有空格),因此所找到的字串是「 are 」、「 rookies 」、「 at 」(前後也都有空格),如何才能找到所有前後有空格的單字呢?請各位讀者自行研究看看!

在進行表單資料驗證之前,我們應先進行表單資料修改,例如拿掉不必要的空格、英文字母大小寫轉換等,這些工作也可以由字串的 replace() 方法或通用式的 exec() 方法來達成,這是我們下一節的主題。


JavaScript 程式設計與應用:用於網頁用戶端